package com.huangfuyi.process.service;

import com.huangfuyi.process.actuator.definition.EPScheduler;
import com.huangfuyi.process.actuator.enums.EPInstanceStatus;
import com.huangfuyi.process.actuator.enums.EPNodeStatus;
import com.huangfuyi.process.actuator.enums.EPNodeType;
import com.huangfuyi.process.actuator.enums.EPParamType;
import com.huangfuyi.process.actuator.threadpool.EPMainThreadPool;
import com.huangfuyi.process.dao.EPCoreInstanceDao;
import com.huangfuyi.process.entity.instance.EPInstanceEntity;
import com.huangfuyi.process.entity.mainthread.EPInstance;
import com.huangfuyi.process.entity.mainthread.EPSchedulerInfo;
import com.huangfuyi.process.entity.model.EPModelEntity;
import com.huangfuyi.process.entity.node.EPNodeEntity;
import com.huangfuyi.process.entity.node.EPNodeNextConfigEntity;
import com.huangfuyi.process.entity.running.EPParamEntity;
import com.huangfuyi.process.entity.running.EPRunningEntity;
import com.huangfuyi.process.entity.running.EPRunningWithParamsEntity;
import com.huangfuyi.process.exceptions.EPDaoException;
import com.huangfuyi.process.exceptions.EPServiceException;
import com.huangfuyi.process.managers.EPManagers;
import com.huangfuyi.process.utils.EPIdUtils;
import com.huangfuyi.process.utils.EPObjectUtils;
import com.huangfuyi.process.utils.EPParamUtils;

import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Logger;

public class EPCoreInstanceService<T extends EPInstanceEntity, E extends EPModelEntity, K extends EPNodeEntity, V extends EPNodeNextConfigEntity, M extends EPRunningEntity, N extends EPParamEntity> {
    private static final Logger log = Logger.getLogger("InstanceService");

    private final EPCoreInstanceDao<T> instanceDao;

    private final EPCoreModelService<E, K, V> modelService;

    private final EPCoreRunningService<M, N> runningService;

    /**
     * 发起流程实例
     */
    public EPInstance create(String instanceId, String instanceName, String modelId, String modelName, Map<String, Object> params) {
        // 当流程模板 id 和 name 同时存在时，优先使用id
        EPModelEntity model;
        if (!EPObjectUtils.isEmpty(modelId)) {
            model = modelService.queryModelById(modelId);
        } else if (!EPObjectUtils.isEmpty(modelName)) {
            model = modelService.queryLastVersionModelByName(modelName);
        } else {
            throw new EPServiceException("流程模板id和流程模板名称不能同时为空。");
        }

        if (EPObjectUtils.isEmpty(instanceId)) {
            instanceId = EPIdUtils.getId();
        }

        // 获取流程模板的开始节点配置
        EPNodeEntity startNode = modelService.queryStartNode(model.getId());

        // 构建并保存运行信息
        EPRunningEntity readyRunning = new EPRunningEntity(EPIdUtils.getId(), instanceId, startNode.getId(), EPNodeStatus.READY, "");
        runningService.add(readyRunning);

        // 保存运行参数
        runningService.saveParams(instanceId, readyRunning.getId(), EPParamType.IN, params);

        // 构建并保存流程实例信息
        EPInstanceEntity instanceEntity = new EPInstanceEntity();
        instanceEntity.setId(instanceId);
        instanceEntity.setName(instanceName);
        instanceEntity.setCreateTime(System.currentTimeMillis());
        instanceEntity.setUpdateTime(System.currentTimeMillis());
        instanceEntity.setModelId(model.getId());
        instanceEntity.setStatus(EPInstanceStatus.READY);
        int add = instanceDao.add(instanceEntity);
        if (add <= 0) {
            throw new EPDaoException("流程实例信息持久化失败。");
        }

        // 启动流程实例
        EPInstance instance = new EPInstance(instanceEntity.getId(), instanceEntity.getName(), instanceEntity.getModelId(), instanceEntity.getStatus(), startNode.getId(), readyRunning.getId(), params);
        EPMainThreadPool.execute(instance);

        return instance;
    }

    /**
     * 暂停流程实例
     */
    public void instanceSuspend(String instanceId) {
        if (EPObjectUtils.isEmpty(instanceId)) {
            throw new EPServiceException("流程实例id不能为空。");
        }

        EPInstanceEntity instanceEntity = instanceDao.queryById(instanceId);
        if (EPObjectUtils.isEmpty(instanceEntity)) {
            throw new EPDaoException(String.format(Locale.ROOT, "无法查询到id为：%s 的流程实例信息。", instanceId));
        }

        EPInstanceStatus status = instanceEntity.getStatus();

        if (!EPInstanceStatus.RUNNING.equals(status)) {
            throw new EPServiceException(String.format(Locale.ROOT, "流程实例：（id：%s； 名称：%s）当前状态不是‘运行中（RUNNING）’，无法暂停。", instanceEntity.getId(), instanceEntity.getName()));
        }

        // 先将流程实例的运行状态修改为暂停中
        int update = instanceDao.updateStatusAndMessage(instanceEntity.getId(), EPInstanceStatus.SUSPENDING, "", System.currentTimeMillis());
        if (update <= 0) {
            throw new EPDaoException(String.format(Locale.ROOT, "暂停流程实例：（id：%s； 名称：%s）时，修改持久层状态为：暂停中 失败。", instanceEntity.getId(), instanceEntity.getName()));
        }

        // 流程实例暂停后，正在运行的自动节点会继续运行完成，后续节点将停止运行
        // 若当前运行的节点为阻断节点，且存在定时配置，即提交到了调度器，则处于等待调度的状态，暂停时需要将其从调度器中移除。
        EPScheduler scheduler = EPManagers.getScheduler();
        List<M> runningInfo = runningService.queryByInstanceId(instanceId);
        for (M runningEntity : runningInfo) {
            EPNodeStatus nodeStatus = runningEntity.getStatus();
            if (EPNodeStatus.SUSPEND.equals(nodeStatus)) {
                try {
                    EPSchedulerInfo schedulerInfo = new EPSchedulerInfo();
                    schedulerInfo.setRunningId(runningEntity.getId());
                    schedulerInfo.setInstanceId(runningEntity.getInstanceId());
                    scheduler.cancel(schedulerInfo);
                } catch (Throwable t) {
                    log.warning(String.format(Locale.ROOT, "暂停流程实例时，取消阻断节点（节点id：%s）的调度任务失败。失败原因：%s", runningEntity.getNodeId(), t.getMessage()));
                }
            }
        }

        // 将流程实例运行状态修改为暂停
        update = instanceDao.updateStatusAndMessage(instanceId, EPInstanceStatus.SUSPEND, "", System.currentTimeMillis());
        if (update <= 0) {
            throw new EPDaoException(String.format(Locale.ROOT, "暂停流程实例：（id：%s； 名称：%s）时，修改持久层状态为暂停 失败。", instanceEntity.getId(), instanceEntity.getName()));
        }
    }

    /**
     * 流程实例暂停后重启
     */
    public void instanceResume(String instanceId) {
        if (EPObjectUtils.isEmpty(instanceId)) {
            throw new EPServiceException("流程实例id不能为空。");
        }

        EPInstanceEntity instanceEntity = instanceDao.queryById(instanceId);
        if (EPObjectUtils.isEmpty(instanceEntity)) {
            throw new EPDaoException(String.format(Locale.ROOT, "无法查询到id为：%s 的流程实例信息。", instanceId));
        }

        EPInstanceStatus status = instanceEntity.getStatus();

        if (!EPInstanceStatus.SUSPEND.equals(status)) {
            throw new EPServiceException(String.format(Locale.ROOT, "流程实例：（id：%s； 名称：%s）当前状态不是‘暂停（SUSPEND）’，无法重启。", instanceEntity.getId(), instanceEntity.getName()));
        }

        // 先将流程实例状态改为就绪
        int update = instanceDao.updateStatusAndMessage(instanceEntity.getId(), EPInstanceStatus.READY, "", System.currentTimeMillis());
        if (update <= 0) {
            throw new EPDaoException(String.format(Locale.ROOT, "重启流程实例：（id：%s； 名称：%s）时，修改持久层状态为：就绪 失败。", instanceEntity.getId(), instanceEntity.getName()));
        }

        // 将暂停状态的节点全部提交到执行器
        EPScheduler scheduler = EPManagers.getScheduler();
        List<M> runningInfo = runningService.queryByInstanceId(instanceId);
        for (M runningEntity : runningInfo) {
            EPNodeStatus nodeStatus = runningEntity.getStatus();
            if (EPNodeStatus.SUSPEND.equals(nodeStatus)) {
                K nodeInfo = modelService.queryNodeById(instanceEntity.getModelId(), runningEntity.getNodeId());
                // 若当前节点为阻断节点，且配置了调度信息，则在重启流程实例时，将其提交到调度器
                if (EPNodeType.TASK_MAN.equals(nodeInfo.getNodeType()) && !EPObjectUtils.isEmpty(nodeInfo.getSchedulerConfig())) {
                    // 查询运行参数，调度配置可能为参数值
                    EPRunningWithParamsEntity runningWithParamsEntity = runningService.queryWithParamsById(runningEntity.getId());
                    Map<String, Object> paramsIn = runningWithParamsEntity.getParamsIn();
                    EPSchedulerInfo schedulerInfo = new EPSchedulerInfo();
                    schedulerInfo.setRunningId(runningEntity.getId());
                    schedulerInfo.setInstanceId(runningEntity.getInstanceId());
                    schedulerInfo.setSchedulerConfig(String.valueOf(EPParamUtils.getValueDefaultKey(paramsIn, nodeInfo.getSchedulerConfig())));
                    scheduler.submit(schedulerInfo);
                }
            }
        }

        // 将流程实例的运行状态修改为运行中
        update = instanceDao.updateStatusAndMessage(instanceEntity.getId(), EPInstanceStatus.RUNNING, "", System.currentTimeMillis());
        if (update <= 0) {
            throw new EPDaoException(String.format(Locale.ROOT, "重启流程实例：（id：%s； 名称：%s）时，修改持久层状态为：运行中 失败。", instanceEntity.getId(), instanceEntity.getName()));
        }
    }

    /**
     * 阻断节点触发
     */
    public void nodeResume(String runningId, Map<String, Object> params, String message) {
        if (EPObjectUtils.isEmpty(runningId)) {
            throw new EPServiceException("触发阻断节点时，运行时id不能为空。");
        }

        EPRunningWithParamsEntity runningWithParamsEntity = runningService.queryWithParamsById(runningId);
        if (EPObjectUtils.isEmpty(runningWithParamsEntity)) {
            throw new EPDaoException(String.format(Locale.ROOT, "未查询到节点的运行时信息。运行时id：%s", runningId));
        }

        T instance = instanceDao.queryById(runningWithParamsEntity.getInstanceId());
        if (EPObjectUtils.isEmpty(instance)) {
            throw new EPDaoException(String.format(Locale.ROOT, "未查询到流程实例信息。当前节点所属流程实例id：%s", runningWithParamsEntity.getInstanceId()));
        }
        if (!EPInstanceStatus.RUNNING.equals(instance.getStatus())) {
            throw new EPServiceException(String.format(Locale.ROOT, "当前节点所属流程实例（id: %s； name： %s）状态为：%s；仅状态为运行中的流程实例可以触发阻断节点。", instance.getId(), instance.getName(), instance.getStatus().name()));
        }

        // 更新运行参数
        if (EPObjectUtils.isEmpty(params)) {
            Map<String, Object> paramsIn = runningWithParamsEntity.getParamsIn();
            paramsIn.putAll(params);
            runningService.saveParams(runningWithParamsEntity.getInstanceId(), runningWithParamsEntity.getId(), EPParamType.IN, paramsIn);
        }

        // 修改节点运行状态为ready
        runningService.updateAndAppendMessage(runningWithParamsEntity.getId(), EPNodeStatus.READY, message);

        // 将节点提交到调度器立即执行，流程框架将立即执行也委托调度器完成，分布式调度的情况下可实现负载均衡
        EPScheduler scheduler = EPManagers.getScheduler();
        EPSchedulerInfo schedulerInfo = new EPSchedulerInfo();
        schedulerInfo.setRunningId(runningWithParamsEntity.getId());
        schedulerInfo.setInstanceId(runningWithParamsEntity.getInstanceId());
        scheduler.runNow(schedulerInfo);
    }

    /**
     * 使用id查询流程实例信息
     */
    public T queryById(String instanceId) {
        if (EPObjectUtils.isEmpty(instanceId)) {
            throw new EPServiceException("流程实例id不能为空。");
        }

        T instanceEntity = instanceDao.queryById(instanceId);
        if (EPObjectUtils.isEmpty(instanceEntity)) {
            throw new EPDaoException(String.format(Locale.ROOT, "没有查询到流程实例（id： %s）的信息。", instanceId));
        }

        return instanceEntity;
    }

    /**
     * 更新流程实例状态，并追加message
     */
    public void updateStatusAndMsg(String instanceId, EPInstanceStatus status, String message) {
        if (EPObjectUtils.isEmpty(instanceId)) {
            throw new EPServiceException("流程实例id不能为空。");
        }
        if (EPObjectUtils.isEmpty(status)) {
            throw new EPServiceException("流程实例目标状态不能为空。");
        }
        int update = instanceDao.updateStatusAndMessage(instanceId, status, message, System.currentTimeMillis());
        if (update <= 0) {
            throw new EPDaoException(String.format(Locale.ROOT, "持久层更新流程实例（id：%s）状态为：%s 失败。", instanceId, status));
        }
    }


    public EPCoreInstanceService(EPCoreInstanceDao<T> instanceDao, EPCoreModelService<E, K, V> modelService, EPCoreRunningService<M, N> runningService) {
        this.instanceDao = instanceDao;
        this.modelService = modelService;
        this.runningService = runningService;
    }
}
